import type { ComponentStyleIdType } from '../../../../types/interfaces'
import type {
  CommentInfo,
  ContentHashes,
  ExtractContentOptions,
  QuoteNavigationConfig,
  QuoteNavigationPlatformConfig,
} from './types'
import { BaseModule } from '../../../../lib/abstract'
import { PageHandler } from '../../../../lib/page_handler'
import { GreasyForkPageType } from '../../../../lib/page_handler/platform/greasyfork/types'
import { ToolKit } from '../../../../lib/utils/toolkit'
import {
  BodyAttrs,
  ComponentStyleId,
  HOSTNAME,
  logger,
  SUPPORTED_PLATFORMS,
} from '../../../../types/constants'
import { SYMBOLS } from '../../../../types/interfaces'
import { ConfigManager } from '../../../config_manager'
import { ConversationFlowReport } from './service/report'
import { ConversationFlowUI } from './service/ui'
import { ConversationFlowUser } from './service/user'
import { ExtractPositionType,
} from './types'

interface Config {
  enabled?: boolean
  debug?: boolean
  printStatistics?: boolean
  printMatchInfo?: boolean // 打印匹配信息
  maxContentLength?: number // 用于生成哈希的最大内容长度
  hashAlgorithm?: 'simple' // 推荐使用简单算法，性能更好
  showQuoteLinks?: boolean // 显示引用链接
  showQuotedByLinks?: boolean // 显示被引用链接
  prefixRatio?: number
  suffixRatio?: number
  enableUnmatchedQuoteDetection?: boolean // 启用未匹配引用检测
  unmatchedQuoteMinLength?: number // 未匹配引用的最小长度阈值
  enableFuzzyMatch?: boolean // 启用模糊匹配
  fuzzyMatchThreshold?: number // 模糊匹配阈值 (0-1)
}

// 测试链接：
// 【油叉】
// 1）https://greasyfork.org/zh-CN/discussions/greasyfork/278992
// 2）https://greasyfork.org/zh-CN/scripts/553387-youtube-playback-plox/discussions/313165
// 会存在 6 条未匹配引用（#7、#31、#35、#42、#44、#58），暂不修复
export class ConversationFlow extends BaseModule {
  // @Decorators.Identity
  readonly id: string = 'conversation_flow'
  // @Decorators.PublicState
  isInitialized: boolean = false
  private static instance: ConversationFlow
  // @Decorators.InheritedConfig
  readonly supportedPlatforms: string[] = ConfigManager.getInstance().moduleSupportedPlatforms(this.name)
  // @Decorators.CoreDependency
  readonly userService: ConversationFlowUser = ConversationFlowUser.getInstance()
  readonly uiService: ConversationFlowUI = ConversationFlowUI.getInstance(this)
  readonly reportService: ConversationFlowReport = ConversationFlowReport.getInstance(this)
  // @Decorators.Config
  readonly styleId: string = ComponentStyleId[this.name]

  config: Config = {
    enabled: true,
    debug: false,
    printStatistics: false,
    printMatchInfo: false,
    maxContentLength: 110,
    hashAlgorithm: 'simple',
    showQuoteLinks: true,
    showQuotedByLinks: true,
    prefixRatio: 0.5,
    suffixRatio: 0.5,
    enableUnmatchedQuoteDetection: true,
    unmatchedQuoteMinLength: 10,
    enableFuzzyMatch: true,
    fuzzyMatchThreshold: 0.85,
  }

  // 存储评论信息
  commentsMap: Map<string, CommentInfo> = new Map()
  private contentHashMap: Map<string, string[]> = new Map() // 内容哈希到评论 ID 的映射
  // 模糊匹配结果缓存
  private fuzzyMatchCache: Map<string, { timestamp: number, results: Array<{ commentId: string, similarity: number }> }> = new Map()
  private readonly FUZZY_MATCH_CACHE_TTL = 60000 // 1分钟缓存

  get name(): ComponentStyleIdType {
    return 'ConversationFlow'
  }

  /**
   * 获取单例实例
   */
  public static getInstance(): ConversationFlow {
    if (!ConversationFlow.instance) {
      ConversationFlow.instance = new ConversationFlow()
    }
    return ConversationFlow.instance
  }

  handleCleanupEventListeners(): void {
    this.uiService.clickEventListeners.forEach((_listener, elementId) => {
      $(`#${elementId}`).off(this.uiService.clickEventNamespace)
    })
  }

  handleCleanupStyles(): void {
    this.unregisterStyle(this.styleId)
  }

  handleCleanupReferences(): void {
    this.commentsMap.clear()
    this.contentHashMap.clear()
    this.userService.clear()
    this.uiService.clickEventListeners.clear()
  }

  core(): void {
    this.loadConfig()

    if (!this.config.enabled)
      return

    this.handleQuoteNavigation()
    // 注册样式
    this.registerStyle(this.styleId, this.getStyles())
  }

  onToggleCallback(_id: string, checked: boolean, first: boolean): void {
    super.onToggleCallback(_id, checked, first)
  }

  handleQuoteNavigation(): void {
    const config: QuoteNavigationPlatformConfig = {
      [SUPPORTED_PLATFORMS.GREASY_FORK]: {
        [GreasyForkPageType.SCRIPT_DISCUSSION]: {
          selector: '#script-content > .comment',
          userName: '.avatar-name span',
          userUrl: '.avatar-name',
          content: '.user-content',
          postTime: '.comment-meta .comment-meta-item:contains("发布于") relative-time[datetime]',
          quoteBlock: 'blockquote',
          sequenceInsert: '.comment-meta .comment-meta-spacer',
        },
        [GreasyForkPageType.DISCUSSIONS_DETAIL]: {
          selector: '.text-content > .comment',
          userName: '.avatar-name span',
          userUrl: '.avatar-name',
          content: '.user-content',
          postTime: '.comment-meta .comment-meta-item:contains("发布于") relative-time[datetime]',
          quoteBlock: 'blockquote',
          sequenceInsert: '.comment-meta .comment-meta-spacer',
        },
      },
    }

    const currentConfig = config[HOSTNAME]?.[PageHandler.getCurrentPageType()]

    if (!currentConfig) {
      logger.warn(`[${this.name}] 未找到当前页面类型的配置，跳过处理`)
      return
    }

    logger.important(`[${this.name}] 开始生成引用导航`)
    this.collectCommentsInfo(currentConfig)
    this.buildUserCommentSequences()
    this.buildQuoteRelationships(currentConfig)
    this.uiService.renderLinks(currentConfig)

    if (this.config.printStatistics)
      this.reportService.printStatistics()
  }

  /**
   * 收集所有评论信息
   */
  private collectCommentsInfo(config: QuoteNavigationConfig): void {
    const { selector, content, postTime } = config

    $(selector).each((index, item) => {
      const floorNumber = index + 1
      const $element = $(item)
      const id = $element.attr('id')

      if (!id) {
        logger.warn(`[${this.name}] 未找到元素 ID，跳过处理`, $element)
        return
      }

      const timeContent = $element.find(postTime).attr('datetime')

      if (!timeContent) {
        logger.warn(`[${this.name}] 未找到发布时间信息，跳过处理`, $element)
        return
      }

      const $content = $element.find(content)

      // 生成多重哈希
      const fullContent = this.extractContentText($content, config.quoteBlock, { type: ExtractPositionType.Full })
      const hashes = this.generateContentHashes($content, config.quoteBlock)

      const commentInfo: CommentInfo = {
        id,
        floorNumber,
        user: {
          name: $element.find(config.userName).text().trim() || '匿名',
          id: $element.find(config.userUrl).attr('href')?.split('/').pop() || '',
        },
        element: $element,
        content: fullContent,
        contentHash: hashes.fullHash,
        prefixHash: hashes.prefixHash,
        suffixHash: hashes.suffixHash,
        timestamp: timeContent,
        quoteTargets: [],
        quotedBy: [],
        hasUnmatchedQuotes: false,
        unmatchedQuoteCount: 0,
        unmatchedQuotePreviews: [],
        matchTypes: {
          exact: [],
          fuzzy: [],
          prefixSuffix: [],
        },
      }

      if (this.config.debug)
        logger.info(`[${this.name}] [${commentInfo.floorNumber}] [元素文本] ${fullContent}`)

      this.commentsMap.set(id, commentInfo)

      // 将评论添加到用户服务中
      this.userService.addCommentToUserSequence(commentInfo)

      // 建立多重哈希映射
      this.updateContentHashMap(hashes, id)

      logger.debug(`[${this.name}] 收集评论信息`, {
        id,
        floorNumber,
        contentHash: hashes.fullHash,
        prefixHash: hashes.prefixHash,
        suffixHash: hashes.suffixHash,
      })
    })

    logger.info(`[${this.name}] ${SYMBOLS.STATUS.SUCCESS} 共收集 ${this.commentsMap.size} 条评论信息`)
  }

  /**
   * 更新内容哈希映射表
   */
  private updateContentHashMap(hashes: ContentHashes, commentId: string): void {
    // 更新完整内容哈希映射
    if (!this.contentHashMap.has(hashes.fullHash)) {
      this.contentHashMap.set(hashes.fullHash, [])
    }
    this.contentHashMap.get(hashes.fullHash)!.push(commentId)

    // 更新前部分内容哈希映射
    const prefixMapKey = `prefix:${hashes.prefixHash}`
    if (!this.contentHashMap.has(prefixMapKey)) {
      this.contentHashMap.set(prefixMapKey, [])
    }
    this.contentHashMap.get(prefixMapKey)!.push(commentId)

    // 更新后部分内容哈希映射
    const suffixMapKey = `suffix:${hashes.suffixHash}`
    if (!this.contentHashMap.has(suffixMapKey)) {
      this.contentHashMap.set(suffixMapKey, [])
    }
    this.contentHashMap.get(suffixMapKey)!.push(commentId)
  }

  /**
   * 构建用户评论序列关系
   */
  private buildUserCommentSequences(): void {
    this.userService.buildAllUserCommentNavigations()

    // 将导航信息更新到评论信息中
    this.commentsMap.forEach((commentInfo, commentId) => {
      const userNavigation = this.userService.getCommentUserNavigation(commentId)

      if (userNavigation) {
        commentInfo.userNavigation = userNavigation
      }
    })

    // 打印用户统计信息
    const userStats = this.userService.getUserStatistics()
    logger.debug(`[${this.name}] ${SYMBOLS.STATUS.SUCCESS} 用户评论关系构建完成`, userStats)
  }

  /**
   * 建立引用关系
   *
   * @param config - 平台配置
   * @private
   */
  private buildQuoteRelationships(config: QuoteNavigationConfig): void {
    const { quoteBlock } = config
    let relationshipCount = 0
    let processedOuterQuotes = 0

    // 重置匹配统计
    this.reportService.resetMatchStatistics()

    this.commentsMap.forEach((commentInfo, commentId) => {
      const $element = commentInfo.element
      const $allQuoteBlocks = $element.find(quoteBlock) // 查找所有的引用块

      if ($allQuoteBlocks.length > 0) {
        // 收集最外层的引用块
        const $outerQuoteBlocks = this.getOutermostQuoteBlocks($allQuoteBlocks, quoteBlock)

        if ($outerQuoteBlocks.length > 0) {
          processedOuterQuotes++

          $outerQuoteBlocks.each((_, quoteElement) => {
            const $quoteElement = $(quoteElement)
            const quoteHashes = this.generateQuoteHashes($quoteElement, quoteBlock)
            const fullQuote = this.extractQuoteText($quoteElement, quoteBlock, { type: ExtractPositionType.Full })

            if (this.config.debug)
              logger.info(`[${this.name}] [${commentInfo.floorNumber}] [引用文本] ${fullQuote}`)

            const minLength = this.config.unmatchedQuoteMinLength || 10
            if (fullQuote.length < minLength) {
              this.handleUnmatchedQuote(commentInfo, fullQuote, 'too_short', {
                quoteLength: fullQuote.length,
                minLength,
              })

              this.reportService.matchStatistics.failed++
              return
            }

            const { commentId: quotedCommentId, matchType } = this.findQuotedComment(quoteHashes, fullQuote, commentInfo.timestamp)

            if (quotedCommentId && quotedCommentId !== commentId) {
              // 记录匹配类型到评论信息中
              this.recordMatchType(commentInfo, quotedCommentId, matchType)

              // 避免重复添加相同引用关系
              if (!commentInfo.quoteTargets.includes(quotedCommentId)) {
                commentInfo.quoteTargets.push(quotedCommentId)

                const quotedComment = this.commentsMap.get(quotedCommentId)
                if (quotedComment && !quotedComment.quotedBy.includes(commentId)) {
                  quotedComment.quotedBy.push(commentId)
                  relationshipCount++

                  logger.debug(`[${this.name}] ${SYMBOLS.STATUS.SUCCESS} 建立引用关系`, {
                    from: commentId,
                    to: quotedCommentId,
                    quoteLength: fullQuote.length,
                    quotePreview: `${fullQuote.substring(0, 30)}...`,
                    nestingLevel: this.getQuoteNestingLevel($quoteElement, quoteBlock),
                  })
                }
              }
            }
            else {
              const reason = quotedCommentId === commentId ? 'self_reference' : 'match_failed'
              this.reportService.matchStatistics.failed++
              this.handleUnmatchedQuote(commentInfo, fullQuote, reason, {
                quotedCommentId,
                quoteHashes,
              })
            }
          })
        }
      }
    })

    logger.info(`[${this.name}] ${SYMBOLS.STATUS.SUCCESS} 建立 ${relationshipCount} 个引用关系，处理 ${processedOuterQuotes} 个最外层引用`)
  }

  /**
   * 记录匹配类型到评论信息中
   */
  private recordMatchType(commentInfo: CommentInfo, targetCommentId: string, matchType: string): void {
    const targetComment = this.commentsMap.get(targetCommentId)
    if (!targetComment)
      return

    // 记录到源评论（引用者）
    if (matchType === 'exact') {
      commentInfo.matchTypes.exact.push(targetCommentId)
    }
    else if (matchType === 'fuzzy') {
      commentInfo.matchTypes.fuzzy.push(targetCommentId)
    }
    else if (matchType === 'prefix-suffix') {
      commentInfo.matchTypes.prefixSuffix.push(targetCommentId)
    }
  }

  /**
   * 统一处理未匹配的引用块
   * @param commentInfo - 评论信息对象
   * @param fullQuote - 完整的引用文本
   * @param reason - 未匹配原因
   * @param extraData - 额外的调试数据
   */
  private handleUnmatchedQuote(
    commentInfo: CommentInfo,
    fullQuote: string,
    reason: 'too_short' | 'match_failed' | 'self_reference',
    extraData?: Record<string, any>,
  ): void {
    if (!this.config.enableUnmatchedQuoteDetection) {
      return
    }

    // 生成引用预览文本
    const preview = this.generateUnmatchedQuotePreview(fullQuote, reason, extraData)

    // 更新评论信息
    commentInfo.hasUnmatchedQuotes = true
    commentInfo.unmatchedQuoteCount++
    commentInfo.unmatchedQuotePreviews.push(preview)

    // 记录调试日志
    this.logUnmatchedQuote(commentInfo, fullQuote, reason, extraData)
  }

  /**
   * 生成未匹配引用的预览文本
   * @param fullQuote - 完整的引用文本
   * @param reason - 未匹配原因
   * @param extraData - 额外的调试数据
   * @returns {string} 格式化的预览文本
   */
  private generateUnmatchedQuotePreview(
    fullQuote: string,
    reason: string,
    extraData?: Record<string, any>,
  ): string {
    const quotePreview = fullQuote

    switch (reason) {
      case 'too_short':
      {
        const quoteLength = extraData?.quoteLength || fullQuote.length
        const minLength = extraData?.minLength || 10
        return `[过短:${quoteLength}/${minLength}字符] ${fullQuote}`
      }

      case 'self_reference':
        return `[自引用] ${quotePreview}`

      case 'match_failed':
        return `[匹配失败] ${quotePreview}`

      default:
        return `[${reason}] ${quotePreview}`
    }
  }

  /**
   * 记录未匹配引用的调试日志
   * @param commentInfo - 评论信息对象
   * @param fullQuote - 完整的引用文本
   * @param reason - 未匹配原因
   * @param extraData - 额外的调试数据
   */
  private logUnmatchedQuote(
    commentInfo: CommentInfo,
    fullQuote: string,
    reason: string,
    extraData?: Record<string, any>,
  ): void {
    const baseLogData = {
      commentId: commentInfo.id,
      floorNumber: commentInfo.floorNumber,
      quoteLength: fullQuote.length,
      quotePreview: `${fullQuote.substring(0, 30)}...`,
      reason,
    }

    const logData = { ...baseLogData, ...extraData }

    switch (reason) {
      case 'too_short':
        logger.debug(`[${this.name}] ${SYMBOLS.STATUS.WARNING} 引用文本过短，计入未匹配统计`, logData)
        break

      case 'self_reference':
        logger.debug(`[${this.name}] ${SYMBOLS.STATUS.WARNING} 发现自引用，计入未匹配统计`, logData)
        break

      case 'match_failed':
        logger.debug(`[${this.name}] ${SYMBOLS.STATUS.WARNING} 发现未匹配的引用块`, logData)
        break

      default:
        logger.debug(`[${this.name}] ${SYMBOLS.STATUS.WARNING} 发现未匹配的引用块 (原因: ${reason})`, logData)
    }
  }

  /**
   * 获取最外层的引用块
   *
   * @param $allQuoteBlocks - 所有的引用块元素
   * @param quoteBlock - 引用块选择器
   * @returns {JQuery<HTMLElement>} 最外层的引用块集合
   * @private
   */
  private getOutermostQuoteBlocks($allQuoteBlocks: JQuery<HTMLElement>, quoteBlock: string): JQuery<HTMLElement> {
    const outermostBlocks: HTMLElement[] = []

    $allQuoteBlocks.each((_, block) => {
      const $block = $(block)

      // 检查当前block是否被其他blockquote包含
      const isNested = $block.parents(quoteBlock).length > 0

      if (!isNested) {
        outermostBlocks.push(block)
      }
    })

    return $(outermostBlocks)
  }

  /**
   * 获取引用块的嵌套层级
   * @param $quoteElement - 引用块元素
   * @param quoteBlock - 引用块选择器
   * @returns {number} 嵌套层级（0表示最外层）
   */
  private getQuoteNestingLevel($quoteElement: JQuery<HTMLElement>, quoteBlock: string): number {
    return $quoteElement.parents(quoteBlock).length
  }

  /**
   * 查找被引用的评论 - 短路匹配逻辑，按优先级依次尝试，匹配成功立即返回
   */
  private findQuotedComment(quoteHashes: ContentHashes, fullQuote: string, currentTimestamp: string): { commentId: string, matchType: string } {
    // 1. 精确匹配（哈希完全一致）
    const exactMatch = this.findExactMatch(quoteHashes.fullHash, currentTimestamp)
    if (exactMatch) {
      const result = { commentId: exactMatch, matchType: 'exact' }
      this.reportService.matchStatistics.exact.add(exactMatch)

      if (this.config.printMatchInfo) {
        logger.info(`[${this.name}] 精确匹配成功，跳过后续匹配`, result)
      }

      return result
    }

    // 2. 模糊匹配（相似度 > 阈值）
    const fuzzyMatch = this.findFuzzyMatch(quoteHashes.fullHash, fullQuote, currentTimestamp)
    if (fuzzyMatch) {
      const result = { commentId: fuzzyMatch, matchType: 'fuzzy' }
      this.reportService.matchStatistics.fuzzy.add(fuzzyMatch)

      if (this.config.printMatchInfo) {
        logger.info(`[${this.name}] 模糊匹配成功，跳过后续匹配`, result)
      }
      return result
    }

    // 3. 前后缀匹配（前后部分都匹配）
    const prefixSuffixMatch = this.findPrefixSuffixMatch(quoteHashes.prefixHash, quoteHashes.suffixHash, currentTimestamp)
    if (prefixSuffixMatch) {
      const result = { commentId: prefixSuffixMatch, matchType: 'prefix-suffix' }
      this.reportService.matchStatistics.prefixSuffix.add(prefixSuffixMatch)

      if (this.config.printMatchInfo) {
        logger.info(`[${this.name}] 前后缀匹配成功，跳过后续匹配`, result)
      }
      return result
    }

    return { commentId: '', matchType: '' }
  }

  /**
   * 精确匹配 - 完整内容哈希匹配
   */
  private findExactMatch(fullHash: string, currentTimestamp: string): string | null {
    const potentialMatches = this.contentHashMap.get(fullHash) || []
    const validMatches = this.filterByTimestamp(potentialMatches, currentTimestamp)

    if (validMatches.length > 0) {
      logger.debug(`[${this.name}] ${SYMBOLS.STATUS.SUCCESS} 精确匹配成功`, {
        matches: validMatches.length,
      })
      return validMatches[0]
    }

    return null
  }

  /**
   * 模糊匹配 - 基于文本相似度的匹配
   *
   * @param quoteHash - 引用文本的哈希
   * @param fullQuote - 引用文本
   * @param currentTimestamp - 当前评论时间戳
   * @returns {string | null} 匹配的评论ID
   */
  private findFuzzyMatch(quoteHash: string, fullQuote: string, currentTimestamp: string): string | null {
    if (!this.config.enableFuzzyMatch) {
      return null
    }

    const quoteText = fullQuote
    const threshold = this.config.fuzzyMatchThreshold || 0.95

    // 检查缓存
    const cacheKey = `${quoteHash}_${currentTimestamp}`
    const cached = this.fuzzyMatchCache.get(cacheKey)

    if (cached && Date.now() - cached.timestamp < this.FUZZY_MATCH_CACHE_TTL) {
      const validResult = cached.results.find((result) => {
        const comment = this.commentsMap.get(result.commentId)
        return comment && new Date(comment.timestamp) < new Date(currentTimestamp)
      })
      if (validResult && validResult.similarity >= threshold) {
        logger.debug(`[${this.name}] ${SYMBOLS.STATUS.SUCCESS} 模糊匹配成功 (缓存)`, {
          commentId: validResult.commentId,
          similarity: validResult.similarity,
          threshold,
        })
        return validResult.commentId
      }
    }

    const potentialMatches = this.getAllPotentialMatches(currentTimestamp)
    let bestMatch: { commentId: string, similarity: number } | null = null

    for (const commentId of potentialMatches) {
      const targetComment = this.commentsMap.get(commentId)
      if (!targetComment)
        continue

      const similarity = ToolKit.string.calculateSimilarity(quoteText, targetComment.content)

      if (similarity >= threshold) {
        if (!bestMatch || similarity > bestMatch.similarity) {
          bestMatch = { commentId, similarity }
        }
      }
    }

    // 更新缓存
    if (bestMatch) {
      this.fuzzyMatchCache.set(cacheKey, {
        timestamp: Date.now(),
        results: bestMatch ? [bestMatch] : [],
      })
    }

    if (bestMatch) {
      logger.info(`[${this.name}] ${SYMBOLS.STATUS.SUCCESS} 模糊匹配成功`, {
        commentId: bestMatch.commentId,
        similarity: bestMatch.similarity,
        threshold,
        quoteLength: quoteText.length,
        targetLength: this.commentsMap.get(bestMatch.commentId)?.content.length,
      })
      return bestMatch.commentId
    }

    return null
  }

  /**
   * 前后部分匹配 - 前部分和后部分都匹配
   */
  private findPrefixSuffixMatch(prefixHash: string, suffixHash: string, currentTimestamp: string): string | null {
    const prefixMatches = this.contentHashMap.get(`prefix:${prefixHash}`) || []
    const suffixMatches = this.contentHashMap.get(`suffix:${suffixHash}`) || []

    // 找到同时满足前后部分匹配的评论
    const intersectionMatches = prefixMatches.filter(id =>
      suffixMatches.includes(id),
    )

    const validMatches = this.filterByTimestamp(intersectionMatches, currentTimestamp)

    if (validMatches.length > 0) {
      logger.debug(`[${this.name}] ${SYMBOLS.STATUS.SUCCESS} 前后部分匹配成功`, {
        matches: validMatches.length,
      })
      return validMatches[0]
    }

    return null
  }

  /**
   * 获取所有可能的匹配评论（按时间过滤）
   * @param currentTimestamp - 当前时间戳
   * @returns {string[]} 评论ID数组
   */
  private getAllPotentialMatches(currentTimestamp: string): string[] {
    const matches: string[] = []

    this.commentsMap.forEach((comment, commentId) => {
      if (new Date(comment.timestamp) < new Date(currentTimestamp)) {
        matches.push(commentId)
      }
    })

    return matches
  }

  /**
   * 根据时间戳过滤匹配结果
   */
  private filterByTimestamp(commentIds: string[], currentTimestamp: string): string[] {
    return commentIds.filter((commentId) => {
      const comment = this.commentsMap.get(commentId)
      return comment && new Date(comment.timestamp) < new Date(currentTimestamp)
    })
  }

  /**
   * 获取匹配类型
   */
  getMatchType(quoteHashes: ContentHashes, quotedComment: CommentInfo): string {
    if (quoteHashes.fullHash === quotedComment.contentHash) {
      return 'exact'
    }
    else if (quoteHashes.prefixHash === quotedComment.prefixHash
      && quoteHashes.suffixHash === quotedComment.suffixHash) {
      return 'prefix-suffix'
    }
    else if (quoteHashes.prefixHash === quotedComment.prefixHash) {
      return 'prefix'
    }
    else if (quoteHashes.suffixHash === quotedComment.suffixHash) {
      return 'suffix'
    }
    return 'unknown'
  }

  /**
   * 提取内容文本
   */
  private extractContentText(
    $content: JQuery<HTMLElement>,
    quoteBlock: string,
    options: ExtractContentOptions = { type: ExtractPositionType.Full,
    },
  ): string {
    const { type = ExtractPositionType.Full, maxLength = this.config.maxContentLength || 200 } = options
    const $clonedContent = $content.clone() // 克隆元素避免修改原 DOM

    $clonedContent.find(quoteBlock).remove() // 移除blockquote内容（引用部分）

    return this.extractText($clonedContent, type, maxLength)
  }

  /**
   * 提取引用文本
   */
  private extractQuoteText(
    $quoteElement: JQuery<HTMLElement>,
    quoteBlock: string,
    options: ExtractContentOptions = { type: ExtractPositionType.Full },
  ): string {
    const { type = ExtractPositionType.Full, maxLength = this.config.maxContentLength || 200 } = options
    const $clonedQuote = $quoteElement.clone()

    $clonedQuote.find(quoteBlock).remove()

    return this.extractText($clonedQuote, type, maxLength)
  }

  /**
   * 提取文本
   */
  extractText($element: JQuery<HTMLElement>, type: ExtractPositionType, maxLength: number): string {
    // 处理链接
    this.processLinks($element)

    // 处理段落
    this.processParagraphs($element)

    let text = $element.text()
      .replace(/\n/g, ' ') // 将换行符替换为空
      .replace(/\s+/g, ' ') // 将多个空白字符替换为单个空格
      .trim()

    // 处理常见的 HTML 实体
    text = ToolKit.string.decodeHtmlEntities(text)

    const cleanedText = text.trim()
    const fullText = text.substring(0, maxLength).trim()

    switch (type) {
      case 'prefix':
      {
        const prefixLength = Math.floor(cleanedText.length * (this.config.prefixRatio || 0.5))
        return cleanedText.substring(0, prefixLength).trim()
      }
      case 'suffix':
      {
        const suffixLength = Math.floor(cleanedText.length * (this.config.suffixRatio || 0.5))
        const suffixStart = Math.max(0, cleanedText.length - suffixLength)
        return cleanedText.substring(suffixStart).trim()
      }
      case 'full':
      default:
        return fullText
    }
  }

  /**
   * 处理链接元素
   */
  private processLinks($element: JQuery<HTMLElement>): void {
    $element.find('a').each((_, linkElement) => {
      const $link = $(linkElement)
      const content = $link.attr(BodyAttrs.LINK_CONTENT) || ''

      if (!content)
        return

      // 替换链接内容为原始文本
      $link.replaceWith(content)
    })
  }

  /**
   * 处理段落元素
   */
  private processParagraphs($element: JQuery<HTMLElement>): void {
    $element.find('p').each((_, paragraphElement) => {
      const $paragraph = $(paragraphElement)
      const content = $paragraph.text()

      if (!content)
        return

      // 替换段落内容为原始文本 + 空格
      $paragraph.replaceWith(`${content} `)
    })
  }

  /**
   * 生成内容的多重哈希值
   */
  private generateContentHashes($content: JQuery<HTMLElement>, quoteBlock: string): ContentHashes {
    const fullContent = this.extractContentText($content, quoteBlock, { type: ExtractPositionType.Full })
    const prefixContent = this.extractContentText($content, quoteBlock, { type: ExtractPositionType.Prefix })
    const suffixContent = this.extractContentText($content, quoteBlock, { type: ExtractPositionType.Suffix })

    return {
      fullHash: ToolKit.string.generateTextHash(fullContent),
      prefixHash: ToolKit.string.generateTextHash(prefixContent),
      suffixHash: ToolKit.string.generateTextHash(suffixContent),
    }
  }

  /**
   * 生成引用文本的多重哈希
   */
  private generateQuoteHashes($quoteElement: JQuery<HTMLElement>, quoteBlock: string): ContentHashes {
    const fullQuote = this.extractQuoteText($quoteElement, quoteBlock, { type: ExtractPositionType.Full })
    const prefixQuote = this.extractQuoteText($quoteElement, quoteBlock, { type: ExtractPositionType.Prefix })
    const suffixQuote = this.extractQuoteText($quoteElement, quoteBlock, { type: ExtractPositionType.Suffix })

    return {
      fullHash: ToolKit.string.generateTextHash(fullQuote),
      prefixHash: ToolKit.string.generateTextHash(prefixQuote),
      suffixHash: ToolKit.string.generateTextHash(suffixQuote),
    }
  }

  applyConfig(config: Partial<Config>): void {
    super.updateConfig(config)

    this.handleQuoteNavigation()

    logger.info(`[${this.name}] Config updated and apply`, this.config)
  }

  getStyles(): string {
    return `
    .${this.uiService.sequenceContainerClass} {
      .nav-buttons {
        display: flex;
        align-items: center;
        justify-content: center;
        margin-right: 8px;
        gap: 2px;
        
        .nav-previous, .nav-next {
          background-color: transparent;
          border: none;
          color: var(${this.themeVarService.link.default.text});
          white-space: nowrap;
          
          &:hover {
            color: var(${this.themeVarService.link.default.textHover});
          }
        }
      }
    }

    .${this.uiService.containerClass} {
      .${this.uiService.quoteContainerClass} {
        .quoted-links-container, .quote-links-container {
          display: flex;
          flex-direction: column;
          gap: 6px;
          flex: 1;
          
          .quoted-links, .quote-links {
            display: flex;
            flex-wrap: wrap;
            gap: 4px;
          }
          
          .quote-link, .quoted-link {
            display: inline-flex;
            align-items: center;
            padding: 4px 8px;
            color: white !important;
            text-decoration: none;
            border-radius: 4px;
            font-size: 11px;
          }
        }
        
        .quoted-links-container {
          align-items: flex-start;
          text-align: left;
          
          .quoted-links {
            justify-content: flex-start;
          }
        }
        
        .quote-links-container {
          align-items: flex-end;
          text-align: right;
          
          .quote-links {
            justify-content: flex-end;
          }
        }
      }
    }
`
  }
}
